//保持内存管理所需的全局参数的定义 By YBR
#ifndef MEMORY_H
#define MEMORY_H
#ifndef NULL
    #define NULL (void *)0
#endif
//PageSize以Byte为单位, 12bit
#define PAGE_SIZE 4096
//内存大小4MB
#define PHY_MEM_SIZE (1<<22)
//内存可以载入的页数 1024 = PHY_MEM_SIZE / PAGE_SIZE
#define PHY_PAGE_NUM (PHY_MEM_SIZE/PAGE_SIZE) 
//虚拟内存大小（也是虚拟地址位数）32位 (注需用ll，否则会超int)，实际测试建议改小一点。
#define VIR_MEM_SIZE (1uLL<<32)
//虚拟内存页数
#define VIR_PAGE_NUM (VIR_MEM_SIZE/PAGE_SIZE)
//TLB最大项数
#define TLB_SIZE 10
//TLB访问时间
#define TLB_ACCESS_TIME 1
//内存访问时间
#define MEM_ACCESS_TIME 10
//帧分配间隔时间 5-11 YBR
#define FRAME_ALLOC_INT 100
//进程初始逻辑内存空间stack pointer默认值
#define SP_INIT 0x40000000
//进程初始逻辑内存空间heap pointer默认值
#define HP_INIT 0x50000000
typedef struct{
    char v;//valid bit
    char d;//dirty bit
    short access;//Access control, 第0位表示是否可读，第1位表示是否可写，……（默认内核态进程不受影响）
    unsigned int PPN;//Physical Page Number, [0, PHY_PAGE_NUM)
}PageTableEntry;
typedef struct{
    PageTableEntry e[VIR_PAGE_NUM];
}PageTable;
typedef struct VPI{
    int pid;//address space, 标识进程, 与pid相同
    unsigned VPN;//virtual page number
    struct VPI *next;
}VPI;//Virtual Page dentifier，唯一标识符，是待转换目标。查TLB，替物理内存页改TLB,PT需要。
typedef struct{
    int pid;
    unsigned VPN;
    short ref;//指向该帧的页表项，用于Copy-on-Write
    VPI *refSrc;//指向其的非pid的页表项，以链表形式存储
    unsigned int blk;//来源的磁盘区块 
}FrameEntry;
typedef enum{
    MEM_KERNAL = 0, //kernal需要的权限
    MEM_READ = 1,
    MEM_WRT = 2,
    MEM_EXE = 4,
}MemoryAccessLevel;

//Physical Frames，存储物理内存每帧的状态（占有者）。
extern FrameEntry frame_table[PHY_PAGE_NUM];//这里不用PMem主要觉得frame_table更合适
extern unsigned char PMem[PHY_MEM_SIZE];
//缺页错误率等与数据与进程相关，写到NOW.h的process里
//初始化
void init_PMem();
PageTable *init_pageTable();
void *destroy_pageTable(PageTable *pt);
//针对WriteBack策略，获取最新的PageTableEntry，若内存中获取失败返回0，否则1，可能获取invalid的页表项。由MMU实现。
int get_latest_PTE(int pid, unsigned vpn, PageTableEntry *pte, int T);
//下面是FrameEntry_refSrc链表的封装函数，无需考虑malloc问题
VPI *new_frame_refSrc(int pid, unsigned vpn, VPI *next);//根据属性创建的新的VPI，并返回的指针
VPI pop_frame_refSrc(VPI **phead);//释放并删除refSrc链表的第一项并返回其值，需输入refSrc的指针，因为可能对链表有处理
void del_specific_frame_refSrc(VPI **phead, int pid, unsigned VPN);
void del_frame_refSrc(VPI *head);//释放并删除refSrc链表的所有成员。
//下面是关于对单个frame的修改。凡是分配/释放内存必须调用allocate_frame,release_frame或allocate_frames
    //清空再重新分配某frame（包含对proc->allocated_frames相关处理），若需要进行清空则返回1，否则返回0
int allocate_frame(unsigned ppn, FrameEntry fe, int T);
    //强制删除指定的一块frame，proc记录的分配帧数,影响TLB，页表所有指向其的项。返回值0表示目标为空闲块无需释放，1表示成功释放。
int release_frame(unsigned ppn, int T);
//在PageReplace实现，每次访问frame时调用(MMU)，为页面替换算法提供数据。
void PR_UpdateFrame(unsigned ppn, int T);
/*-----------以下不在同名.c文件中实现-----------*/
//malloc， 只改虚拟虚拟内存。
unsigned allocate_memory(int pid, unsigned size, int T);
//帧分配, 返回本次分配帧数; 采用动态形式，进程建立后每隔一段时间进行allocate
int allocate_frames(int pid, int T);//位于FrameAllocator.c
//MMU地址转换。内含TLB, pageFault等内容, 返回值为处理结果, 1正常，0出现段错误。位于MMU.c
int get_physical_address(int pid, unsigned virtual_address,short accessLevel,unsigned* physical_address, int T);
//帧替换算法，pid=-1时在全局寻找，否则在pid相关寻找；返回选择的frame号，若查找失败(如输入不存在的pid)返回-1.
int victim_frame(int pid, int algorithm);
//回写frame。由mmap实现。
int write_back_frame(int ppn,int T);
// void page_fault(int pid, int T, int diskBlock);
/*-----------暴露在外的必要接口(可调用上述基础接口)-----------*/
int Mmap(int pid, int T);
/*

进程创建时调用，表示进程pid在T时刻被创建
需要完成的动作：
1.初始化Process结构体中的PTBR，根据job输入的最大绝对时间为PTBR分配空间(这里需要讨论分配页数的依据，比如一个时间单元对应多少页)
2.完成对PageTable初始化，同时把PageTable载入内存
这里的问题是：如果严格按照操作系统定义，就需要把PT载入内存，但这就需要我们能够访问内存每个字节的内容，与VPI设立的初衷不符
故考虑直接在Process中分配空间（仅个人意见，实际实现可灵活）
3.分配帧数，修改frame_table
4.完成正确的日志输出（建议文件输出）
5.返回值表示是否正确载入进程

*/

int Load(int pid, unsigned Vaddr, int T);

/*

进程pid在T时间想要访问虚拟地址Vaddr（32位）上的值
需要完成：
1.地址转换
2.判断是否发生缺页并解决
3.输出正确日志
4.返回值表示是否发生段错误（保证测试用例会访问非法地址）

注意：子进程继承父进程的PTBR，所以记得判断该进程是否有父进程

*/
int Store(int pid, unsigned Vaddr, int T);

/*

进程pid在T时间想要修改虚拟地址Vaddr（32位）上的值
需要完成：
1.地址转换
2.判断是否发生缺页并解决
3.修改脏位
4.输出正确日志
5.返回值表示是否发生段错误

注意：子进程继承父进程的PTBR，所以记得判断该进程是否有父进程，如果有就要copy_on_write


*/

int Munmap(int pid, int T);

/*

进程结束时调用，
需要完成以下动作：
1.释放PT
2.释放帧数，修改PMem
3.输出正确日志
4.返回值表示是否正确结束进程

*/

#endif